local Module_EVALVEBUS = {}

local dp = Module_GETDATA.data_point
---------------------阀门状态常量-------------------------

local V_OFFLINE = 255 -- 阀门离线
local V_CLOSE = 0 -- 阀门全关
local V_OPEN = 100 -- 阀门全开

local V_OFFLINE_THRESHOLD = 2 -- 离线判断阈值
local valve_offline_cnter = {0, 0, 0, 0} -- 阀门离线判断确认标识，每次识别到离线，增加1，否则清0

--------基于UART1的modbus485配置----------
local uart_id = 1
--- modbus 端口配置------
local uart_baud = 115200 -- 波特率
local RX_BUF_LEN = 1024 -- rxbuffer length
local TX_RX_PIN = 24 -- SP3485 收发转换引脚
local PWR_EN_PEN = 22 -- 阀门供电MOS控制引脚，高电平有效
local MODBUS_REC_MAX_LEN = 100

local CTRL_485_DIR = gpio.setup(TX_RX_PIN, 0, gpio.PULLUP)
local CTRL_PWR_EN = gpio.setup(PWR_EN_PEN, 0, gpio.PULLUP)

---接收阀位信息 #BESTPOSA------
local RX_I_HDR = 1      -- RTK应答帧头 #BESTPOSA 索引
local LAT_IDX = 12      -- 纬度在分隔后数组的索引      
local LNG_IDX = 13      -- 经度在分隔后数组的索引



local RX_I_VID = 2 -- 阀门ID  A-D
local RX_I_VOP = 4 -- 阀门开度 000-100 必须是三位整数
local VOP_LEN = 3 -- 阀门开度数据宽度，必须是3位数

local VALVE_ID_MIN = 1          -- 总线阀门ID最小值
local VALVE_ID_MAX = 4          -- 总线阀门ID最大值

local EVALVE_PWR_THRESHOLD = 3.6 -- 总线阀门上电阈值，通过检测AIR724供电状态推算当前MCU的LDO是否供电正常，低于4.2表示电池已经处于电压状态
-- 此时给总线阀门控制板供电可能导致EEPROM时序错乱导致误写，ID一般为0XA0,磁场零点一般为160
Module_EVALVEBUS.is_valve_operating = false -- 阀门是否被用户操控， true = 正在操控 flase = 操作完毕，阀门停止状态，只有在FALSE时可被publis状态

--[[
    modbus read 协程
    获取并解析土壤墒情数据
]]
function modbus485_init()

    ---------------配置 UART1,并绑定TXRX切换引脚--------------
    uart.setup( uart_id, -- 串口id
                uart_baud, -- 波特率
                8, -- 数据位
                1, -- 停止位
                uart.None, -- 无奇偶校验
                uart.LSB, -- 小端模式
                RX_BUF_LEN, -- 接收缓存
                TX_RX_PIN, -- rs485 收发切换引脚
                0, -- rx使能引脚的电平 Receiver Output Enable Active LOW
                2000 -- 收发转换时间 9600波特率为 2000
              )

    uart.on(uart_id, "receive", function(id, len)

        local s = ""
        repeat
            s = uart.read(id, RX_BUF_LEN)

            if #s > 0 then -- #s 是取字符串的长度
                sys.publish("msg_modbus_recdata", #s, s) -- 发送消息到modbus处理函数
                -- log.info("uart", "receive", id, #s, s:toHex())   --
            end
        until s == ""
    end)

end

--[[
功能  : 通过循环定时器检查阀门是否离线
参数  ：无


返回值：无
]]
-- local function valve_check_offline()

--     if valve_offline_cnter[1] < V_OFFLINE_THRESHOLD then
--         valve_offline_cnter[1] = valve_offline_cnter[1] + 1
--     else
--         dp.v1 = V_OFFLINE
--     end

--     if valve_offline_cnter[2] < V_OFFLINE_THRESHOLD then
--         valve_offline_cnter[2] = valve_offline_cnter[2] + 1
--     else
--         dp.v2 = V_OFFLINE
--     end

--     if valve_offline_cnter[3] < V_OFFLINE_THRESHOLD then
--         valve_offline_cnter[3] = valve_offline_cnter[3] + 1
--     else
--         dp.v3 = V_OFFLINE
--     end

--     if valve_offline_cnter[4] < V_OFFLINE_THRESHOLD then
--         valve_offline_cnter[4] = valve_offline_cnter[4] + 1
--     else
--         dp.v4 = V_OFFLINE
--     end

--     -- log.warn("valve offline conter", valve_offline_cnter[1],
--     --          valve_offline_cnter[2], valve_offline_cnter[3],
--     --          valve_offline_cnter[4])
-- end

-- 定时检查阀门是否离线----
-- sys.timerLoopStart(valve_check_offline, -- 记录轮询超时次数，标记没有ACK的阀门，收到ACK离线离线计数器清零
--                    MODBUS_EVALVE_QUERY_TIME / (V_OFFLINE_THRESHOLD - 1))

--[[
    更新阀门数据点的值

@function   valve_state_update
@param      valveid : 阀门地址: 1-4
            valve_open_percent: 阀门开度： 0-100
@return     无
@usage

]]
local function valve_state_update(valveid, valve_openDeg)

    if valve_openDeg > V_OFFLINE then -- 在极限归零位置，导致阀门开度返回超大值，此阀位为负值
        valve_openDeg = -10 -- 为了保证符号一致性，出现大值是直接赋值-100 -1更合理
    end

    if valveid == 1 then
        dp.v1 = valve_openDeg
        valve_offline_cnter[1] = 0 -- 清零离线标志计数器
    end
    if valveid == 2 then
        dp.v2 = valve_openDeg
        valve_offline_cnter[2] = 0 -- 清零离线标志计数器
    end
    if valveid == 3 then
        dp.v3 = valve_openDeg
        valve_offline_cnter[3] = 0 -- 清零离线标志计数器
    end
    if valveid == 4 then
        dp.v4 = valve_openDeg
        valve_offline_cnter[4] = 0 -- 清零离线标志计数器
    end
    log.warn("valve offline conter", valve_offline_cnter[1],
             valve_offline_cnter[2], valve_offline_cnter[3],
             valve_offline_cnter[4])

end

--[[
    更新阀门数据点的值

@function   split
@param      input : 待分隔字符串
            delimiter: 分隔字符
@return     分隔结果列表 table
@usage

]]
-- 分割函数
local function split(str2split, delimiter)
    local result = {}
    for match in (str2split .. delimiter):gmatch("(.-)" .. delimiter) do
        table.insert(result, match)
    end
    return result
end



--[[
    modbus 初始化和接收命令解析 协程


]]
local function init_datapoint_fromDB()
    dp.valve_nmbr = _G.MAX_VALVE_ID
    dp.v1 = _G.V1_OPEN_DEG
    dp.v2 = _G.V2_OPEN_DEG
    dp.v3 = _G.V3_OPEN_DEG
    dp.v4 = _G.V4_OPEN_DEG

end



--[[
    modbus 初始化和接收命令解析 协程


]]
sys.taskInit(function()
    local valveID_table =
        { -- 阀门ACK中第二个字符表明ABCD阀门id转换为1-4阀门序号
            ["A"] = 1,
            ["B"] = 2,
            ["C"] = 3,
            ["D"] = 4
        }

    local bind_ack = {
        ack = "bind",
        uniqId = "868078047341307",
        imei = "868078047341307",
        mac = "",
        iccid = "83405331081604490167"
    }
    local bindqr_ack = {
        ack = "bindqr",
        status = "success"
    }

    local j_cmd = ""

    init_datapoint_fromDB()         --使用kv数据库的值初始化datapoint各个字段

    modbus485_init() -- init modbus485

    while true do
        local res, recLen, recBuf = sys.waitUntil("msg_modbus_recdata",  MODBUS_EVALVE_QUERY_TIME * 2) -- 等待轮询线程发送读取指令

        if res == true then
            recBuf = recBuf:trim()     -- 去除前导后缀空字符，包括\r\n

            ----------------解析阀门应答阀位信息-------------@A:123 \r\n----------------
            if recBuf:sub(RX_I_HDR, RX_I_HDR+8) == "#BESTPOSA" then             -- 校验帧头是不是0X40 即：@符号
                log.info("rtkmsg: ", recLen, recBuf)                            -- 接收到的数据帧
                rtk_data = split(recBuf, ",")                                   -- 用逗号分隔字符串
                log.info("rtk loc(lat,lng): ",  rtk_data[LAT_IDX],  rtk_data[LNG_IDX])    -- 接收到的数据帧
                dp.lat_rtk =  rtk_data[LAT_IDX]                                 -- 更新 RTK纬度
                dp.lng_rtk =  rtk_data[LNG_IDX]                                 -- 更新 RTK经度
            end --endof if recBuf:sub(RX_I_HD

            -------------二维码绑定--------解析绑定APP的json----｛RWEVAL24461BTB91=RW-EVALVE_273844｝
            if recBuf:startsWith("{") and recBuf:endsWith("}") then
                local payload, res = json.decode(recBuf)
                -- log.info("uart rxcallback", "json cmd: ", payload, res)
                if res == 1 then
                    j_cmd = payload.cmd
                    if j_cmd == "bind" then                     --   { "cmd": "bind"  }
                        log.info("uart rxcallback", "bind cmd received ", j_cmd, res)
                        local module_imei = mobile.imei()
                        local module_imsi = mobile.imsi()
                        local module_iccid = mobile.iccid()


                        bind_ack.imei  = module_imei                                 --- 用IMEI的后6个byte拼接成TLINK 的 SN号
                        bind_ack.iccid = module_iccid                               --- 用IMEI的后6个byte拼接成TLINK 的 SN号
                        -- bind_ack.mac   = module_imsi                               --- 用IMEI的后6个byte拼接成TLINK 的 SN号
                        -- bind_ack.uniqId = "RW-EVALVE_" .. module_imei:sub(-6)       --- 用IMEI的后6个byte拼接成TLINK 的 SN号
                        bind_ack.uniqId = module_imei       --- 用IMEI的后6个byte拼接成TLINK 的 SN号
                        local json_bind_ack = json.encode(bind_ack)
                        uart.write(uart_id, json_bind_ack)      -- 发送 设备信息JSON
                        uart.write(uart_id, "\r\n")      -- 发送 设备信息JSON

                    end

                    if j_cmd == "bindqr" then            --   {"cmd":"bindqr", "data":"RWEVAL24ABCDEFMN"}
                        dp.qruid = payload.data
                        local write_res = fskv.set(DB_K_QRUID, dp.qruid)    -- 将QRUID写入KV数据库中
                        log.info("uart rxcallback", "qruid = ", #dp.qruid, dp.qruid)

                        if write_res == true then
                            bindqr_ack.status = "success"
                        else
                            bindqr_ack.status = "db_write_err"
                        end
                        local json_bindqr_ack = json.encode(bindqr_ack)
                        uart.write(uart_id, json_bindqr_ack)        -- 发送 设备信息JSON
                        uart.write(uart_id, "\r\n")                 -- 发送 设备信息JSON

                        sys.wait(2*1000)                            -- 等待uart发送ack
                        sys.publish(MSG_REBOOT)  --重启设备

                    end
                end

            end

        end -- endof if res == true.


    end -- endof while true do
end -- sys.taskInit
) -- 创建modbus485接收机ACK解析task

-----------------------MODBUS -------------------------------

--[[
--- 发送总线阀门控制命令
?A:qury0d0a
$A:0000d0a
$A:0100d0a
注意:命令必须以odoa结束
@function   rs485_valve_query
@param      vid : 阀门地址: ABCD
            cmd:  命令功能码 ctrl,query
            cmdParam : 阀门开度值 0-100
@return     无
@usage
发送modbus 查询命令
]]
-- local function rs485_valve_query(vid)
--     -- local queryCmd = "?A:qury"  阀门状态查询命令
--     local queryCmd = "?"
--     if vid == 'A' or vid == 'B' or vid == 'C' or vid == 'D' then
--         queryCmd = queryCmd .. vid .. ":qury" -- 根据vid拼接各个阀门的qury命令
--     end

--     local data_tx = queryCmd .. string.char(0x0d) .. string.char(0x0a)
--     uart.write(uart_id, data_tx) -- 发送查询命令
--     -- local data = (string.format("%02x", vid) ..
--     --                  string.format("%02x", cmd) ..
--     --                  string.format("%04x", reg) .. string.format("%04x", value)):fromHex() -- 将格式化十六进制字符串转换为hex序列
--     -- local modbus_crc_data = pack.pack('<h', crypto.crc16("MODBUS", data))
--     -- local data_tx = data .. modbus_crc_data -- 拼接为hex序列: 0x01 0x03 0x00 0x00 0x00 0x0a 0xc5 0xcd
--     -- uart.write(uart_id, data_tx) -- 发送查询命令
-- end

--[[
  收到阀门控制命令和QUERY命令后，打开MOS供电，之后启动定时，当定时器超时调用此CALLBACK
  关闭MOS降低功耗
]]
local function timer_rs485valve_pwr_timeout_Cb()
    CTRL_PWR_EN(0) -- 动作定时器超时，关断电源降低功耗
    Module_EVALVEBUS.is_valve_operating = false

end
--[[
--- 发送总线阀门控制命令
?A:qury0d0a
$A:0000d0a
$A:0100d0a
注意:命令必须以odoa结束
@function   rs485_valve_query
@param      vid : 阀门地址: ABCD
            cmd:  命令功能码 ctrl,query
            cmdParam : 阀门开度值 0-100
@return     无
@usage
发送modbus 查询命令
]]
function rs485_valve_ctrl(vid, openningDegree)
    Module_EVALVEBUS.is_valve_operating = true
    CTRL_PWR_EN(1) -- 开启RS485总线阀门供电
    sys.wait(2000)
    Module_EVALVEBUS.valvePwrTimer = sys.timerStart(timer_rs485valve_pwr_timeout_Cb, MODBUS_EVALVE_ACTION_TIME) -- 防止协程处理loop超时阻塞， 类似看门狗定时器，重复调用，重置定时器

    -- local ctrlCmd = "$A:qury"  阀门状态查询命令
    local ctrlCmd = "$"
    if vid == 'A' or vid == 'B' or vid == 'C' or vid == 'D' then
        ctrlCmd = ctrlCmd .. vid .. string.format(":%03d", openningDegree) -- 根据vid拼接各个阀门的qury命令
    end

    local data_tx = ctrlCmd .. string.char(0x0d) .. string.char(0x0a)
    uart.write(uart_id, data_tx) -- 发送查询命令
end

-- --[[
--     modbus read 协程
--     获取并解析土壤墒情数据
-- ]]
-- function valve_status_query()
--     CTRL_PWR_EN(1) -- 开启RS485总线阀门供电
--     sys.wait(2 * 1000)

--     log.warn("periold query task, query all vlave ", "1 -", dp.valve_nmbr)
--     for id = VALVE_ID_MIN, VALVE_ID_MAX do                  -- 阀门轮询
--         if id > dp.valve_nmbr then                          -- 动态跳过未安装阀门，默认1拖2，当操作了3-4阀门更新dp.valve_nmbr
--             break
--         end
--         rs485_valve_query(string.char(string.byte('A') - 1 + id))
--         sys.wait(500)                                       -- 轮询间隔
--     end

--     valve_check_offline()                                   -- 每次轮询更新离线计数器

--     ------------save valve status to db---------------
--     --------------------------------------------------

--     if Module_EVALVEBUS.is_valve_operating == false then
--         log.info("valve_status_query", "valve move flag = ", Module_EVALVEBUS.is_valve_operating)
--         CTRL_PWR_EN(0)
--     end
--     -- sys.timerStart(timer_rs485valve_pwr_timeout_Cb, MODBUS_EVALVE_ACTION_TIME) -- 防止协程处理loop超时阻塞， 类似看门狗定时器，重复调用，重置定时器


-- end

--[[
功能
参数  ：


返回值：无
]]
-- local function tsk_valve_query()

--     --------------------- SUPER LOOP ----------------
--     while true do

--         sys.waitUntil("notify_valve_query",MODBUS_EVALVE_QUERY_TIME)        -- 轮询周期是rssi命令线程的一半MODBUS_EVALVE_QUERY_TIME
--                                                                             -- 收到notify_valve_query消息，接触阻塞立即应答rssi=1招测

--         valve_status_query() -- 开启RS485总线阀门供电,轮询阀门状态
--         log.warn("valve current status:", dp.v1, dp.v2, dp.v3, dp.v4)

--         sys.publish("msg_mqtt_upload")                                      -- 通知mqtt协程上传数据



--     end
-- end

-- 启动串口数据轮询任务
-- sys.taskInit(tsk_valve_query) -- 创建轮询task

--[[
    {"sensorDatas":[{"sensorsId":4265109,"value":"1","flag":"v1"}],"down":"down"}
    函数原型：local function decode_cmd_and_ctrl_valve(cmdstr)
    功能描述：解析TLINK发送给阀门的控制命令，并将控制量0-100映射到 0-10
             格式： v1=99
             注意没有空格
    参数列表：阀门控制字符串

    返回列表： v_name, v_ctrlDeg 阀门id: 1-4 阀门开度: 0-10

]]
local function decode_cmd_and_ctrl_valve(cmd_type, cmd_val)
    cmdtype_to_vid = {v1 = 1, v2 = 2, v3 = 3, v4 = 4}

    v_id = cmdtype_to_vid[cmd_type] -- 查表的方式将cmd_type v1-v4转换为1-4

    v_ctrlDeg = tonumber(cmd_val) -- 阀门控制开度： 0-10 其中0为关闭 10为全开，2-8根据电阻阵列反馈电压控制开度
    v_ctrlDeg = math.floor(v_ctrlDeg) -- 微信小程序发 0-100， 消除小数

    log.warn("cmd content: k v ", v_id, v_ctrlDeg) -- 将ctrl_cmd表中存的 阀门ID和控制量(0-10)提取出来

    return v_id, v_ctrlDeg -- 返回阀门名称 vx, 开度控制量 v_ctrlDeg 0-10
end


--[[
功能
参数  ：
{"sensorDatas":[{"sensorsId":4265109,"value":"1","flag":"v1"}],"down":"down"}


返回值：无
]]
local function tsk_valve_ctrl()

    local cmd_cnt = 0
    local vid_table = {"A", "B", "C", "D", "NULL"}

    --------------------- SUPER LOOP ----------------
    while true do

        local res, __topic__, payload = sys.waitUntil("msg_mqtt_cmd", MQTT_CMD_WAIT_TIME)
        if res == true then
            log.info("tsk_valve_ctrl", "MQTT cmd Received: ", #payload, payload)
            if payload:startsWith("{") and payload:endsWith("}") then

                local tjsondata, result, errinfo = json.decode(payload) -- 解析json数据
                local cmd_type = tjsondata["sensorDatas"][1].flag  -- 命令类型， V1-V4  rssi=0 - 1
                local cmd_val  = tjsondata["sensorDatas"][1].value -- value字段

                -- 发送控制消息给阀门控制任务
                -- sys.publish("valveCtrlMsg",tjsondata["sensorDatas"][1].flag .. "=" .. tjsondata["sensorDatas"][1].value) --阀门控制命令： vx=1 or vx=0

                -- {"sensorDatas":[{"sensorsId":4265115,"value":"1","flag":"rssi"}],"down":"down"}
                if cmd_type == "rssi" then -- 复位命令
                    if cmd_val == "-1" then
                        log.info("tsk_valve_ctrl",
                            "收到rssi=-1 !!! 阀门最大数量恢复到1个！！！")
                        dp.valve_nmbr = 1 -- 阀门数量恢复到1拖1，仅轮询主机阀门减少轮询次数
                        dp.v2 = DB_V_V2_OPEN_DEG_INIT -- 阀门2-4初始化为离线
                        dp.v3 = DB_V_V3_OPEN_DEG_INIT
                        dp.v4 = DB_V_V4_OPEN_DEG_INIT

                        fskv.set(DB_K_MAX_VALVE_ID,dp.valve_nmbr)
                        fskv.set(DB_K_V1_OPEN_DEG, dp.v1)
                        fskv.set(DB_K_V2_OPEN_DEG, DB_V_V2_OPEN_DEG_INIT)
                        fskv.set(DB_K_V3_OPEN_DEG, DB_V_V3_OPEN_DEG_INIT)
                        fskv.set(DB_K_V4_OPEN_DEG, DB_V_V4_OPEN_DEG_INIT)
                        sys.publish(MSG_REBOOT)  --重启设备

                    end
                    if cmd_val == "0" then
                        -- log.error("收到rssi=0 !!! 系统复位 .....................")
                        sys.publish(MSG_REBOOT)  --重启设备
                    end
                    if cmd_val == "1" then
                        -- log.info("tsk_valve_ctrl", "收到rssi=1, 招测各个阀门状态。。。。。")
                        -- sys.publish("notify_valve_query", -1) -- 通知modbus阀门状态轮询协程轮询各个阀门状态 valve id= -1表示全部阀门轮询
                        sys.publish("msg_mqtt_upload") -- 阀门控制命令执行完毕，通知TLINK协程pblish ACK
                    end

                -- elseif cmd_type:startsWith("v") then  -- 控制命令 v1 - v4, 必须以v开头
                --     -- 解析命令,返回阀门ID和控制开度
                --     local valve_id, valveCtrlPercent = decode_cmd_and_ctrl_valve(cmd_type, cmd_val)
                --     -- log.warn( "收到mqtt协程命令,解析后：(ID, 目标开度百分比): ", valve_id, valveCtrlPercent)
                --     -- 1对应A阀 2对应B阀 3对应C阀 4对应D阀 通过485总线向对应的阀门发送$X:1230d0a控制命令
                --     if valve_id >= VALVE_ID_MIN and valve_id <= VALVE_ID_MAX then
                --         -- valveCtrlDeg = valveCtrlPercent*0.9    --百分比转换为旋转角度
                --         local valveCtrlDeg = valveCtrlPercent     -- 直接将百分比发送给阀门子机
                --         log.warn("收到mqtt协程命令,解析后：(ID, 目标角度): ", valve_id, valveCtrlDeg)
                --         rs485_valve_ctrl(vid_table[valve_id], valveCtrlDeg)
                --         -- valves_state[valve_id].value = valveCtrlDeg                              --更新阀门状态,如果收到ack则更新为真实值
                --         valve_state_update(valve_id, valveCtrlDeg) -- 直接将控制量赋值给阀门状态，利用刷新确认阀门实际状态
                --         sys.publish("msg_mqtt_upload") -- 阀门控制命令执行完毕，通知TLINK协程pblish ACK
                --         if  valve_id > dp.valve_nmbr  then
                --            dp.valve_nmbr = valve_id                                                 --当阀门控制命令发送了V3,V4，更新阀门熟练，以便轮询对应阀门
                --            fskv.set(DB_K_MAX_VALVE_ID,dp.valve_nmbr)
                --            log.warn("data wite to KV database:" ,"MAX_VALVE_ID=",dp.valve_nmbr )

                --         end

                --     end
                    -- local valve_action_time = math.abs(valveCtrlDeg - dp.v1)
                    -- log.warn("阀门动作等待时间", valve_action_time * 400)
                    -- sys.wait(valve_action_time * 500)
                end -- end of if cmd_type == "rssi=0"

            end -- endof if payload:startsWith("{") and payload:endsWith("}") t

        end --endof if res == true then
    end --endof while true do
end -- endof local function


-- 启动串口数据轮询任务
sys.taskInit(tsk_valve_ctrl) -- 创建轮询task




return Module_EVALVEBUS
